﻿Imports System.Runtime.InteropServices

#Region " MouseHook native API calls "
Module MouseHookNative

    Public Delegate Function HookProc(ByVal code As Integer, ByVal wParam As Int32, ByVal lParam As IntPtr) As Boolean


    Public Structure POINTAPI
        Public x As Long
        Public y As Long
    End Structure

    Public Structure MSLLHOOKSTRUCT
        Public pt As POINTAPI
        Public MouseData As Long
        Public Flags As Long
        Public Time As Long
        Public dwExtraInfo As Long
    End Structure


    Public Const WM_RBUTTONDOWN = &H204
    Public Const WM_RBUTTONUP = &H205
    Private Const WH_MOUSE_LL = 14
    Private Const HC_ACTION = 0

    Public mHandle As Integer

    Public Declare Function UnhookWindowsHookEx Lib "user32" _
     (ByVal hHook As Integer) As Integer

    Public Declare Function SetWindowsHookEx Lib "user32" _
      Alias "SetWindowsHookExA" (ByVal idHook As Integer, _
      ByVal lpfn As HookProc, ByVal hmod As Integer, _
      ByVal dwThreadId As Integer) As Integer

    Private Declare Function GetAsyncKeyState Lib "user32" _
      (ByVal vKey As Integer) As Integer

    Public Declare Function CallNextHookEx Lib "user32" _
      (ByVal hHook As Integer, _
      ByVal nCode As Integer, _
      ByVal wParam As Integer, _
      ByVal lParam As IntPtr) As Integer


End Module
#End Region

Public Class MouseHook

    Private HookID As Integer

#Region " MouseHook helpers "
    Public Const WM_MOUSEMOVE As Integer = &H200
    Public Const WM_LBUTTONDOWN As Integer = &H201
    Public Const WM_RBUTTONDOWN As Integer = &H204
    Public Const WM_MBUTTONDOWN As Integer = &H207
    Public Const WM_LBUTTONUP As Integer = &H202
    Public Const WM_RBUTTONUP As Integer = &H205
    Public Const WM_MBUTTONUP As Integer = &H208
    Public Const WM_LBUTTONDBLCLK As Integer = &H203
    Public Const WM_RBUTTONDBLCLK As Integer = &H206
    Public Const WM_MBUTTONDBLCLK As Integer = &H209
    Public Const WM_MOUSEWHEEL As Integer = &H20A

    <StructLayout(LayoutKind.Sequential)> _
    Public Structure POINT
        Public X As Integer
        Public Y As Integer
    End Structure
    Private Const WH_MOUSE_LL As Integer = 14
    <StructLayout(LayoutKind.Sequential)> _
    Public Structure MouseHookStruct
        Public pt As Point
        Public mouseData As Integer
        Public flags As Integer
        Public time As Integer
        Public dwExtraInfo As IntPtr
    End Structure

#End Region

    Public Event MouseHookCapture As MouseEventHandler


    Public Sub New()
        HookID = SetWindowsHookEx(WH_MOUSE_LL, AddressOf MouseHookProc, _
                 Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()(0)), 0)

        If HookID = 0 Then
            Destroy()
            Throw New Exception("SetWindowsHookEx failed")
        End If
    End Sub

    Protected Overrides Sub Finalize()
        Destroy()
        MyBase.Finalize()
    End Sub

    Protected Sub Destroy()
        If HookID <> 0 Then
            UnhookWindowsHookEx(HookID)
            HookID = 0
        End If
    End Sub

    Protected Function MouseHookProc(ByVal code As Integer, ByVal wParam As Int32, ByVal lParam As IntPtr) As Boolean
        If code >= 0 Then
            SyncLock GetType(MouseHook)
                Dim button As MouseButtons
                Dim clickCount As Integer = 0

                Select Case wParam
                    Case WM_LBUTTONDOWN
                        button = MouseButtons.Left
                    Case WM_RBUTTONDOWN
                        button = MouseButtons.Right
                    Case WM_MBUTTONDOWN
                        button = MouseButtons.Middle
                    Case Else
                        button = MouseButtons.None
                End Select

                If button <> MouseButtons.None Then
                    clickCount = IIf(wParam = WM_LBUTTONDBLCLK Or wParam = WM_RBUTTONDBLCLK, 2, 1)
                End If

                Dim mhs As MouseHookStruct = CType(Marshal.PtrToStructure(lParam, GetType(MouseHookStruct)), MouseHookStruct)
                RaiseEvent MouseHookCapture(Nothing, New MouseEventArgs(button, clickCount, mhs.pt.X, mhs.pt.Y, mhs.mouseData))
            End SyncLock
        End If

        Return CallNextHookEx(HookID, code, wParam, lParam)
    End Function

End Class